package tk.winshu.shortestpath.run;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.winshu.shortestpath.model.Node;
import tk.winshu.shortestpath.model.Runner;

import java.awt.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 运行类
 *
 * @author winshu
 * @date 2015年2月6日
 */
public class RunHandler {

    private static final Logger logger = LoggerFactory.getLogger(RunHandler.class);

    private static final Image RUNNER = Toolkit.getDefaultToolkit().getImage(RunHandler.class.getResource("/image/Runner.png"));

    /**
     * 步长
     */
    private static final int STEP_LENGTH = 8;

    /**
     * 单个线程的线程池，不必重复创建线程
     */
    private Executor threadPoolExecutor = Executors.newSingleThreadExecutor();

    /**
     * 运行者
     */
    private Runner runner;
    /**
     * 是否正在运行
     */
    private boolean isRunning;

    /**
     * 路径
     */
    private List<Integer> paths;

    public RunHandler() {
        this.paths = new ArrayList<>();
        this.runner = new Runner(RUNNER);
    }

    /**
     * 计算下一位置
     *
     * @param current 当前位置
     * @param source  起始位置
     * @param target  终点位置
     * @return 下一位置
     */
    private static Point2D nextLocation(Point2D current, Point2D source, Point2D target) {
        if (current.equals(target)) {
            return source;
        }

        // 如果剩下的距离小于步长，则直接移动到目标点
        if (Math.abs(current.getX() - target.getX()) <= STEP_LENGTH && Math.abs(current.getY() - target.getY()) <= STEP_LENGTH) {
            return target;
        }

        double x1 = source.getX();
        double y1 = source.getY();
        double x2 = target.getX();
        double y2 = target.getY();

        int directionX = x1 < x2 ? 1 : -1;
        int directionY = y1 < y2 ? 1 : -1;

        // 垂直线,k=∞
        if (x1 == x2) {
            return new Point2D.Double(target.getX(), current.getY() + STEP_LENGTH * directionY);
        }

        double k = (y2 - y1) / (x2 - x1);
        double stepX = STEP_LENGTH / (Math.sqrt(k * k + 1));
        double stepY = Math.abs(k) * stepX;

        return new Point2D.Double(current.getX() + stepX * directionX, current.getY() + stepY * directionY);
    }

    public boolean isRunning() {
        return isRunning;
    }

    public void stopAction() {
        this.isRunning = false;
        this.runner.setVisible(false);
        this.paths.clear();
    }

    /**
     * 判断一条直线是否在路中
     */
    public boolean isLineInPath(int startIndex, int endIndex) {
        if (paths == null) {
            return false;
        }
        int pathIndex = paths.indexOf(startIndex);
        if (pathIndex < 0 || pathIndex == paths.size() - 1) {
            return false;
        }
        return endIndex == paths.get(pathIndex + 1);
    }

    /**
     * 运行
     */
    public void startAction(List<Node> nodePaths, boolean keepLoopRunning, ICallback callback, IRepaint repaint) {
        this.paths = nodePaths.stream().map(Node::getIndex).collect(Collectors.toList());
        if (this.paths.isEmpty()) {
            return;
        }

        this.isRunning = true;
        this.runner.setVisible(true);
        threadPoolExecutor.execute(() -> {
            do {
                resetRunner(nodePaths.get(0));
                for (Node node : nodePaths) {
                    // 中断操作
                    if (!isRunning) {
                        break;
                    }
                    runToNode(node, repaint);
                }
            } while (isRunning && keepLoopRunning);
            if (callback != null) {
                // 停止动作
                callback.onFinished();
            }
        });
    }

    /**
     * 重置运行者
     */
    private void resetRunner(Node startNode) {
        if (startNode != null) {
            runner.setLocation(startNode.getLocation());
            runner.setIndex(startNode.getIndex());
        }
    }

    public void drawRunner(Graphics2D g2d) {
        if (runner != null) {
            runner.draw(g2d);
        }
    }

    private void runToNode(Node nextNode, IRepaint repaint) {
        runner.setIndex(nextNode.getIndex());
        Point2D source = runner.getLocation();
        Point2D target = nextNode.getLocation();

        while (!nextNode.getLocation().equals(runner.getLocation())) {
            Point2D nextLocation = RunHandler.nextLocation(runner.getLocation(), source, target);
            runner.setLocation(nextLocation);
            if (repaint != null) {
                repaint.repaint();
            }
            try {
                Thread.sleep(17);
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
                Thread.currentThread().interrupt();
            }
        }
    }
}
